import java.lang.reflect.*;
import java.util.*;
import java.net.*;
import java.io.*;

/*
 *  @author  Alpesh Mistry  <apm1dec@gmail.com>
 *  @author  Ankur Kothari  <a.kothari711@gmail.com>
 *  @author  Elman Mansimov <elman.mansimov@gmail.com>
 *  @author  Michelle Pan   <panmiche@gmail.com>
 *  @version 1.0
 *  @since   2012-03-03 
 */

public class FileSystem {
	
	/**
	 * Creates a new Directory that where all the directories 
	 * and files are stored. It doesn't have any parent. 
	 */
	public static Directory root = new Directory("root", null);
	
	/**
	 * Creates a new Directory that stores the location of 
	 * the current directory 
	 */
	public static Directory currDir = root;
	
	/**
	 * Creates a new FilesSystem that is used to make instance
	 * methods. 
	 */
	private static Class<FileSystem> fs = FileSystem.class;
	
	/**
	 * Creates a new Hashtable which stores the command name
	 * as keys and method names that execute that command.
	 * The keys and values are also used to get the appropriate
	 * manual for the command.
	 */
	private static Hashtable<String, String> cmdType = new Hashtable<String, String>();

	/**
	 * Stores an instance of FileSystem class,
	 * so that methods can be invoked inside it.
	 */
	private static Object obj;

	/**
	 * Used to implement Singleton process on FileSystem.
	 */
	private static FileSystem onlyFileSystem = null;

	/**
	 * Stores a String array that contains the path,
	 * where the changed are to be made. Initialized to
	 * null.
	 */
	private String[] dirPath = null;
	
	/**
	 * Stores a String that contains the directory or file 
	 * name on which the actions are taken.
	 */
	private static String dirName = null;
	
	/**
	 * Stores an int that is used to store the index of any
	 * given array. Initialized to 0.
	 */
	private static int i = 0;
	
	/**
	 * Stores a String that contains the result of any action 
	 * taken by the user.
	 */
	private static String output;
	
	/**
	 * Stores a BufferedReader object initialized to null.
	 * Used when any files are read.
	 */
	private static BufferedReader in = null;
	
	/**
	 * Stores an InputStreamReader object initialized to null.
	 * Used when any files are read.
	 */
	private static InputStreamReader fileRead = null;
	
	/**
	 * Stores a File object initialized to null. Used to store
	 * the file object, when an outfile has been mentioned by the user. 
	 */
	private static File tempContents = null;
	
	/**
	 * Stores an Integer that keeps a count of the word ">" or ">>".
	 */
	private static int counter;

	/*
	 * Used to initialize output, tempContents, counter and cmdType. 
	 */

	private FileSystem() {
		
		output = "";
		tempContents = null;
		counter = 0;
		this.generateCommands();
	}
	
	/**
	 * Used to implement so everyone can use the same FileSystem.
	 * 
	 * It synchronizes everyone using FileSystem. So multiple 
	 * copies or out of date copies are not used concurrently.
	 */

	public static FileSystem getInstance()
	{
	  if (onlyFileSystem == null)
	  {
	    synchronized(FileSystem.class) {
	      FileSystem inst = onlyFileSystem; 
	      if (inst == null)
	      {
	        synchronized(FileSystem.class) {
	        	onlyFileSystem = new FileSystem();               
	        }
	      }
	    }
	  }
	  return onlyFileSystem;
	}

	/**
	 * Store the command names as keys and method names as values in cmdType.
	 * 
	 * This method stores the command names as keys and method names as values
	 * in cmdType. The values are also a part of the name of the file where a 
	 * manual for each command is stored.
	 *
	 * @see java.util.Hashtable
	 */

	private void generateCommands() {
		
		cmdType.put("cd", "changeDirectory");
		cmdType.put("mkdir", "makeDirectory");
		cmdType.put("ls", "printItemLists");
		cmdType.put("pwd", "printCurrentDirectory");
		cmdType.put("mv", "moveItem");
		cmdType.put("cp", "copyItem");
		cmdType.put("cat", "printFileContents");
		cmdType.put("get", "printUrlData");
		cmdType.put("echo", "writeToFile");
		cmdType.put("ln", "linkTo");
		cmdType.put("man", "printManual");
		cmdType.put("find", "findString");
		cmdType.put("grep", "printManual");
		cmdType.put("rm", "removeItem");
		cmdType.put("exit", "exit");
	}

	/**
	 * Calls the method depending on the user input.
	 * 
	 * This method invokes the methods that help execute the appropriate
	 * commands. The command name is a key to the method name is cmdType. 
	 * If the method doesn't exist then an error is raised a message is 
	 * printed. 
	 *
	 * @return String output is returned, which stored the output of the
	 * 				  result of the user input. 
	 * @throws Exception    If an instance of FileSystem can't be made.
	 * 					    If the code tries execute a method, that
	 * 						does not exist in the class or is an abstract method
	 *                      in the class.
	 * 						If the code tries to access methods that it
	 * 						is not allowed to. 
	 * 						Method or object that is set private
	 *                      and doesn't give permission to access it.                                     
	 * 						the given method doesn't exist or can't be invoked.
	 * @see java.lang.reflect
	 */

	public String selectFunction(String[] cmdInput) throws Exception {

		Class<?> [] arguments = {String[].class};
		obj = fs.newInstance();
		Method func = null;

		try {

			func = fs.getDeclaredMethod(cmdType.get(cmdInput[0]), arguments);
			func.invoke(obj, (Object) cmdInput);

		} catch (Exception e) {

			System.out.println("Invalid command or wrong syntax used.");
		e.printStackTrace();
		}
		return output;
	}

	/**
	 * Helper method to create and return a line to help better distinguish the manual.
	 * 
	 * Create line with the character '_' to help better distinguish the manual.
	 * The line is returned as a string.
	 * 
	 * @return String The line that contains multiple '_' characters.
	 */

	private String addLining() {
		
		String line = "";
		for (int j = 0; j < 136; j++)
			line += "_";
		
		line += "\n";

		return line;
	}

	/**
	 * Adds the manual of the given command to a string.
	 * 
	 * Adds the manual of the given command to "output" which is then
	 * either printed or written to a File object, depending on user
	 * input. The manual helps the user better understand the command.
	 * Error is raised when a a command doesn't exist and an appropriate 
	 * is added to a string which is later added to "output".
	 * 
	 * @param cmdHelp String array that stores the commands that the user has asked for.
	 */

	@SuppressWarnings({ "unused" })
	private void printManual(String[] cmdHelp) {

		String fileName = null;
		FileInputStream fileInput = null;
		String cmdDNE = "";
		String[] cmdSorted = sortGuillemet(cmdHelp);

		if (counter == 0 || counter == 1)
			if (cmdSorted.length > 1)
				for (i = 1; i < cmdSorted.length; i++)
					try {

						fileName = cmdType.get(cmdSorted[i]) + ".txt";
						fileInput = new FileInputStream(fileName);
						fileRead = new InputStreamReader(fileInput);
						in = new BufferedReader(fileRead);
						output += addLining() + "\n";
						while(in.ready())
							output += in.readLine() + "\n";

						in.close();
						output += addLining();

					} catch(Exception e) {

						cmdDNE += cmdSorted[i] + ": command does no exist." + "\n";
					}
			else
				detectInvalidCmd(cmdSorted, "man", 1);
		
		outputFile();
		output += cmdDNE;
	}
	
	/**
	 * Adds the "output" to a outfile mentioned by the user.
	 * 
	 * Adds the "output" to a outfile mentioned by the user.
	 * After the output is added to a file outout is set to
	 * an empty string.
	 * 
	 * @param cmdHelp String array that stores the commands 
	 * 		  that the user has asked for.
	 */

	private void outputFile(){

		if (counter == 1) {

			tempContents.appendContents(output);
			output = "";
		}
	}

	/**
	 * Adds the the URL data of the given URLs to a string 
	 * which is later added to File Objects .
	 * 
	 * Adds the URL data to a string which is later added to
	 * their respective File Objects with their names being 
	 * that of the url. Also if an outfile is mentioned then 
	 * all the URL data is also added to the mentioned outfile. 
	 * If "http://" doesn't exist before a link, it is added.
	 * Also prints an appropriate message when URl begin with "https://".
	 * Error is raised when the URL doesn't exist and an appropriate 
	 * message is printed.
	 * 
	 * @param cmdUrl String array that stores the URL that the user has asked for.
	 */

	@SuppressWarnings("unused")
	private void printUrlData(String[] cmdUrl) {

		String[] cmdSorted = sortGuillemet(cmdUrl);
		String urlContents = "";

		if (counter == 0 || counter == 1)
			if (cmdSorted.length > 1)
				for (i = 1; i < cmdSorted.length; i++) {
					try {

						if (cmdSorted[i].startsWith("https:/"))
							System.out.print("Secure URL not supported.");
						else if (!cmdSorted[i].startsWith("http://"))
							cmdSorted[i] = "http://" + cmdSorted[i];

						URL urlName = new URL(cmdSorted[i]);
						fileRead = new InputStreamReader(urlName.openStream());
						in = new BufferedReader(fileRead);

						while (in.ready())
							urlContents += in.readLine() + "\n";

						in.close();
						writeUrlFile(cmdSorted[i], urlContents);
						output += urlContents;
						urlContents = "";

					} catch(Exception e) {
						
						System.out.println("Invalid URL. This URL does not exist.");
						e.printStackTrace();
					}
				}
			else
				detectInvalidCmd(cmdSorted, "get", 1);

		outputFile();
		output = "";
	}
	
	/**
	 * Helper method to add URL data File Object .
	 * 
	 * Adds the URL data to a File Objects with their names being 
	 * that of the URL. Since echo command is called, a copy of 
	 * all required constructor variables are made, which are later
	 * restored to their original values before this method was called.
	 * 
	 * @param fileName String that stores the URL name.
	 * @param contents String that stores the URL data.
	 */

	private void writeUrlFile(String fileName, String contents) {
		
		String copyDirName = dirName;
		int copyCounter = counter;
		counter = 0;
		File copyContents = tempContents;
		String copyOutput = output;
		int copyIndex = i;
		output = "";
		fileName = fileName.replace("/", "");
		fileName = fileName.replace("http:", "");
		contents = contents.substring(0, contents.length() - 1);
		String[] urlFile = {"echo", contents, ">", fileName};
		writeToFile(urlFile);
		dirName = copyDirName;
		counter = copyCounter;
		tempContents = copyContents;
		output = copyOutput;
		i = copyIndex;
	}

	/**
	 * Adds the the path of the current working directory to a string.
	 * 
	 * Adds the the path of the current working directory to a string,
	 * which is later printed or added to an outfile if mentioned by 
	 * the user. Error is raised when the arguments of the command
	 * are not exactly as required.
	 * 
	 * @param currDirPath String array that stores the commands that 
	 * 					  the user has asked for.
	 */

	@SuppressWarnings("unused")
	private void printCurrentDirectory(String[] currDirPath) {

		String[] cmdSorted = sortGuillemet(currDirPath);

		if (counter == 0 || counter == 1)
			if (currDirPath.length == 1)
				if (currDir.getParent() != null)
					output += currDir.getFullPath() + "\n";
				else
					output += "/" + "\n";
			else
				detectInvalidCmd(cmdSorted, "pwd", -1);

		outputFile();
	}

	/**
	 * Changes the working directory.
	 * 
	 * Changes the working directory. If an outfile is mentioned then an empty
	 * String is added to the outfile. If the command is not given correctly 
	 * an appropriate message is printed and/or error is raised.
	 * 
	 * @param changeDir String array that stores path that the user has asked for.
	 */

	@SuppressWarnings("unused")
	private void changeDirectory(String[] changeDir) {

		Directory temp = currDir;
		SystemItem store = null;
		String[] cmdSorted = null;
		cmdSorted = sortGuillemet(changeDir);

		if (counter == 0 || counter == 1)
			if (cmdSorted.length == 1)
				currDir = root;
			else if (cmdSorted.length == 2)
				try {

					dirName = sortPath(cmdSorted[1]);
					if (dirName.equals(""))
						currDir = root;
					else 
	 					try {

	 						currDir = (Directory) currDir.getContents().get(dirName);;

	 					} catch (Exception e) {

	 						System.out.println(cmdSorted[1] + " is a file.");
	 						resetValues(temp);
	 					}
					currDir.getName();

				} catch (Exception e) {

					pathDNE(cmdSorted[1], "");
					resetValues(temp);
				}
			else
				detectInvalidCmd(cmdSorted, "cd", -1);

	outputFile();
	}

	/**
	 * Changes the directory.
	 * 
	 * Changes the directory to the path given and is returned
	 * Error is raised if the directory doesn't exist and null is returned.
	 * 
	 * @param thePath String array that stores path that is used to change the directory.
	 * @param tempDir Directory object that stores the current directory from the change 
	 *                is being done.
	 * @param Directory object or null depending upon whether the directory exists or not.
	 */

	private Directory changeDirHelper(String[] thePath, Directory tempDir) {
		
		String[] thePath2 = null;

		if (thePath.length == 0)
			return tempDir;
		else
		{
			tempDir = (Directory) tempDir.getContents().get(thePath[0]);
			thePath2 = new String[thePath.length-1];
			System.arraycopy(thePath, 1, thePath2, 0, thePath2.length);
			return changeDirHelper(thePath2, tempDir);
		}
	}

	/**
	 * Changes the currDir to the directory mentioned and returns the
	 * name of the last element in the path.
	 * 
	 * Sorts the path and change currDir to the parent directory 
	 * of the last element in the string path. The name of the 
	 * last element in the path is returned
	 *  
	 * @param path String that stores the location where the 
	 * 			   user action has to take place.
	 * @return String Name of the last element in the path;
	 */

	private String sortPath(String path){

		ArrayList<String> pathList = new ArrayList<String>(Arrays.asList(path.trim().split("/")));
		String lastPathElement = "";
		if (pathList.size() == 0 || (pathList.get(0).equals("")))
			currDir = root;

		if (pathList.size() == 0)
			return "";

		while (pathList.contains(""))
			pathList.remove("");
		
		lastPathElement = pathList.get(pathList.size() - 1);
		pathList.remove(pathList.size() - 1);
		dirPath = new String[pathList.size()];
		dirPath = pathList.toArray(dirPath);
		currDir = changeDirHelper(dirPath, currDir);

		return lastPathElement;
	}

	/**
	 * Creates a new directory.
	 * 
	 * Creates a new directory in the working directory or the given path.
	 * If an outfile is mentioned then an empty String is added to the outfile.
	 * If the command is not given correctly an appropriate message is printed
	 * and/or error is raised.
	 * 
	 * @param cmdMake String array that stores the paths and directories to be created.
	 */

	@SuppressWarnings("unused")
	private void makeDirectory(String[] cmdMake) {

		Directory temp = currDir;
		String[] cmdSorted = null;
		cmdSorted = sortGuillemet(cmdMake);

		if (counter == 0 || counter == 1)
			for (i = 1; i < cmdSorted.length; i++) {
				try {

					dirName = sortPath(cmdSorted[i]);
					if (currDir.getLink())
						currDir = (Directory) currDir.getLinkedTo();
					
					if (!currDir.getContents().containsKey(dirName) && !dirName.equals(""))
						currDir.addItem(dirName, new Directory(dirName, currDir));
					else {

						System.out.print("Cannot create directory. `" + dirName);
						System.out.println("\' directory already exists.");
					}
				} catch (Exception e) {

					pathDNE(cmdSorted[i], "Cannot create directory.");
					e.printStackTrace();
				}
				resetValues(temp);
			}
		detectInvalidCmd(cmdSorted, "mkdir", 1);
		outputFile();
	}

	/**
	 * Resets currDir and dirPath to their original value.
	 * 
	 * Resets currDir and dirPath to their original value. 
	 * This is done so that if the currDir is changed for the
	 * required action but not the user's working directory, then
	 * the user automatically goes back to their last location.
	 * 
	 * @param dirValue Directory object that contains the
	 * 				   working directory of the user.
	 */

	private void resetValues(Directory dirValue) {

		currDir = dirValue;
		dirPath = null;
	}

	/**
	 * Check if the user mentioned an outfile.
	 * 
	 * Checks if the user mentioned an outfile in the String array parameter.
	 * If multiple oufiles are mentioned then, error message is printed 
	 * and the user command is not executed. If a single outfile is mentioned 
	 * the output of that command is stored in the outfile. If no outfile is 
	 * mentioned then the command is executed and the output printed. Sorted array
	 * is returned after all traces of outfiles are removed.
	 * 
	 * @param search String array that stores the user input.
	 */

	private String[] sortGuillemet(String[] search){

		String path = null;
		int arrLen = search.length;
		ArrayList<String> contents = new ArrayList<String>();
		String[] sortedCmd = null;
		String guillemet = null;
		contents.add(search[0]);
		int j;

		for (j = 1; j < arrLen; j++)
			if (search[j].equals(">>") || search[j].equals(">")) {

				counter += 1;
				guillemet = search[j];

			} else if (search[j-1].equals(">>") || search[j-1].equals(">"))
				path = search[j];
			else if (!(search[j].equals(">>") || search[j].equals(">")))
				contents.add(search[j]);

		if (counter > 1 || ((counter == 1) && (path == null))) {

			System.out.println("Invalid redirect. There should be just one filename.");
			counter = 100;

		} else if (counter == 1)
			createFile(path, guillemet);

		sortedCmd = new String[contents.size()];
		sortedCmd = contents.toArray(sortedCmd);
		return sortedCmd;
	}

	/**
	 * Create an outfile.
	 * 
	 * Create an outfile at the given path which is stored in 
	 * string parameter pathGiven. Any action such as the contents 
	 * should be appended or overwritten is stored in String parameter
	 * guillType. The resulting outfile is stored in tempContents.
	 * If the mentioned outfile is a directory an appropriate message is printed.
	 * Also a message is printed if the given path of the outfile is invalid.
	 * 
	 * @param pathGiven String that stores the path of the outfile.
	 * @param pathGiven String that stores the action to be taken on the outfile
	 * 					such as append or overwrite contents.
	 */

	private void createFile(String pathGiven, String guillType) {
		
		Directory temp = currDir;
		try {
			dirName = sortPath(pathGiven);
			
			if (!dirName.equals("")) {
				if (currDir.getLink())
					currDir = (Directory) currDir.getLinkedTo();
					
				if (currDir.getContents().containsKey(dirName))
					try{
	
						tempContents = (File) currDir.getContents().get(dirName);
	
						if (guillType.equals(">"))
							tempContents.setContents("");
	
					} catch (Exception e) {
	
						System.out.println(dirName + ": is a directory.");
					}
				else {
	
					tempContents = new File(dirName, currDir);
					currDir.addItem(dirName, tempContents);
					tempContents.setContents("");
				}
			}
		} catch (Exception e) {

			pathDNE("Cannot create File. ", dirName);
		}
		resetValues(temp);
	}

	/**
	 * Writes the contents to a file.
	 * 
	 * Writes the contents to a file depending on user input 
	 * which is located in the working directory or the given path.
	 * Also it takes into account whether the user wants to append 
	 * or overwrite the contents. If the command is not given correctly
	 * an appropriate message is printed or/and error is raised.
	 * 
	 * @param writeFile String array that stores the content to be written
	 *  				to the file and its path.
	 */

	private void writeToFile(String[] writeFile) {

		String[] cmdSorted = sortGuillemet(writeFile);

		if (counter == 0 || counter == 1)
			if (cmdSorted.length > 1)
				for (i = 1; i < cmdSorted.length; i++)
					output += cmdSorted[i] + " ";
		
		output.trim();
		outputFile();
	}

	/**
	 * Raise an error when a command hasn't been entered appropriately
	 * by the user.
	 * 
	 * Raise an error when a command hasn't been entered appropriately
	 * by the user i.e. the number of arguments along with the commands
	 * do not meet the required specifications. The user is asked to open 
	 * the command manual when this happens.
	 * 
	 * @param cmd String array that stores the command entered by the user.
	 * @param cmdName String that stores the name of the command name entered.
	 * @param index Integer that is purposefully used to raise an error.
	 * 				It is relative to the number of arguments of that command. 
	 */

	private void detectInvalidCmd(String[] cmd, String cmdName, int index) {

		try{	

			cmd[index] = cmd[index];

		} catch (Exception e) {

			System.out.println("Invalid arguments. Try 'man " + cmdName + "' for help.");
		}
	}

	/**
	 * Prints an error message when a path does not exist.
	 * 
	 * Prints an error message when a path does not exist.
	 * 
	 * @param path String that stores the path entered by the user.
	 * @param action String that stores the action to be done when in the
	 * 				 given path.
	 */

	private void pathDNE(String path, String action){

		System.out.print(action + "`" + path + "'");
		System.out.println(": No such directory or file exists.");
	}

	/**
	 * Adds the contents of the directory or a file depending on user input
	 * to a String.
	 *  
	 * Adds the contents contents of the directory or path of a file depending
	 * on user input in an alphabetical order which is located in the working 
	 * directory or the given path. If the command is followed by "-R" then
	 * the method adds the all the directories and sub-directories and their 
	 * contents recursively to a string. The list of all the contents is 
	 * received by using a helper method called listItemsHelper. The string 
	 * is later printed to an outfile if mentioned one by the user. If the 
	 * command is not given correctly an appropriate message is printed and/or 
	 * error is raised.
	 * 
	 * @param cmdContents String array that stores the command entered by the user.
	 */

	@SuppressWarnings("unused")
	private void printItemLists(String[] cmdContents) {

		String itemName = null;
		String[] cmdSorted = sortGuillemet(cmdContents);

		if (counter == 0 || counter == 1)
			if (cmdSorted.length == 1)
				output += currDir.printContents(false, currDir.getName());
			else if (cmdSorted[1].equals("-R") && cmdSorted.length > 2)
				listItemsHelper(cmdSorted, true, 2);
			else if (cmdSorted[1].equals("-R") && cmdSorted.length == 2)
				output += currDir.printContents(true, ".");
			else 
				listItemsHelper(cmdSorted, false, 1);
		outputFile();
	}
	
	/**
	 * Adds the contents of the directory or a file mentioned to a String.
	 *  
	 * Adds the contents contents of the directory or path of a file depending
	 * on user input in an alphabetical order which is located in the working 
	 * directory or the given path. If recusive parameter is true then
	 * the method adds the all the directories and sub-directories and their 
	 * contents to a string. the index parameter is given to skip the string "ls" 
	 * and/or "-R". If the path mentioned is incorrect an appropriate 
	 * message is printed and/or error is raised.
	 * 
	 * @param cmdCOntents String array that stores the command entered by the user.
	 */

	private void listItemsHelper(String[] items, boolean recursive, int index) {

		String itemName = null;
		Directory temp = currDir;
		String path = null;

		for (i = index; i < items.length; i++) {

			try {
				itemName = sortPath(items[i]);
				if (itemName.equals(""))
					itemName = ".";
				if (currDir.getContents().containsKey(itemName)) {

					try {
						
						path = ((Directory) currDir.getContents().get(itemName)).getName();
						output += currDir.getContents().get(itemName).printContents(recursive, path);
					
					} catch (Exception e) {
						
						output += currDir.getContents().get(itemName).printContents(false, null);
					}
				} else {

					pathDNE(itemName, "");
				}
				resetValues(temp);

			} catch (Exception e) {

					pathDNE(items[i], "");
			}
			resetValues(temp);
		}
	}

	/**
	 * Adds the contents of the a file to a string.
	 * 
	 * Adds the contents of the a file to a string which is either 
	 * printed added to an outfile depending on user input. 
	 * The file is located in the working directory or a given path. 
	 * If the command is not given correctly an appropriate message is printed
	 * or/and error is raised.
	 * 
	 * @param fileNamePath String array that stores file name and/or the path.
	 */

	@SuppressWarnings("unused")
	private void printFileContents(String[] fileNamePath) {

		Directory temp = currDir;
		String itemName = null;
		String[] cmdSorted = sortGuillemet(fileNamePath);
		File contents = null;

		if (counter == 0 || counter == 1)
			if (cmdSorted.length > 1)
				for (i = 1; i < cmdSorted.length; i++) {

					try {

						itemName = sortPath(cmdSorted[i]);
						if (itemName.equals(""))
							itemName = ".";
							
						if (currDir.getContents().containsKey(itemName)){
								try {
	
									contents = (File) currDir.getContents().get(itemName);
									output += contents.getContents();
	
								} catch (Exception e) {
	
									System.out.println(itemName + ": is a directory.");
								}
							} else {
	
								pathDNE(itemName, "");
							}
						resetValues(temp);

					} catch (Exception e) {

						pathDNE(cmdSorted[i], "");
					}	
				}
			else
				detectInvalidCmd(cmdSorted, "cat", 1);
		
		outputFile();
	}

	/**
	 * Moves the directory or file to the given path.
	 * 
	 * Moves the directory or a file to the given path. All
	 * the links are broken if the original directory or file is being moved.
	 * The moved links are not broken.If the command is not given correctly 
	 * an appropriate message is printed or/and error is raised. If an
	 * outfile is mentioned then an empty String is appended to the outfile contents.
	 * This method uses another method to help move the files and directories called
	 * moveItemHelper.
	 * 
	 * @param movePath String array that stores file or directory names to be
	 * 				   moved to the given destination.
	 */

	@SuppressWarnings("unused")
	private void moveItem(String[] movePath) {

		Directory temp = currDir;
		String itemName = null;
		String[] cmdSorted = sortGuillemet(movePath);
		ArrayList<SystemItem> items = new ArrayList<SystemItem>();

		if (counter == 0 || counter == 1)
			if (cmdSorted.length > 2) {
				for (i = 1; i < cmdSorted.length - 1; i++) {

					try {

						itemName = sortPath(cmdSorted[i]);
						if (itemName.equals(""))
							itemName = ".";

						if (currDir.getContents().containsKey(itemName))
							items.add(currDir.getContents().get(itemName));
						else
							pathDNE(itemName, "");

					} catch (Exception e) {

						pathDNE(cmdSorted[i], "");
					}
					resetValues(temp);
				}
				moveItemHelper(cmdSorted[cmdSorted.length - 1], items);
			} else
				detectInvalidCmd(cmdSorted, "mv", 2);

		outputFile();
	}
	
	/**
	 * Add the directories or files to the target path.
	 * 
	 * Adds the directory or a file to the given path from parameter target.
	 * All the links are broken if the original directory or file is being moved.
	 * The moved links are not broken. If the target doesn't exist an appropriate 
	 * message is printed or/and error is raised. The items to be moved are removed 
	 * from their parent directory contents and their parent directories are changed
	 * to the target directory. The links are not broken if the source directory and 
	 * target directory are same.
	 * 
	 * @param target String that stores the target destination where 
	 * 				 the files or directories will be moved.
	 * @param itemsMoved ArrayList<SystemItem> that stores the files or directories 
	 * 					 being moved.
	 */

	private void moveItemHelper(String target, ArrayList<SystemItem> itemsMoved) {

		Directory temp = currDir;
		String itemName = null;
		Iterator<SystemItem> itr = itemsMoved.iterator();
		Directory parentItem = null;
		Directory storeTemp = null;
		SystemItem eachItem = null;

		try {

			itemName = sortPath(target);
			if (itemName.equals(""))
				itemName = ".";
			if (currDir.getContents().containsKey(itemName)){
				storeTemp = (Directory) currDir.getContents().get(itemName);

				if (storeTemp.getLink())
					storeTemp = (Directory) storeTemp.getLinkedTo();

				while (itr.hasNext()) {

					eachItem = itr.next();
					if (! storeTemp.getContents().containsKey(eachItem.getName())) {
						
						parentItem = eachItem.getParent();
						parentItem.getContents().remove(eachItem.getName());
						eachItem.setParent(storeTemp);
						eachItem.getContents();
						storeTemp.addItem(eachItem.getName(), eachItem);

						if (!storeTemp.getFullPath().equals(parentItem.getFullPath()))
							if (!eachItem.getLink())
								eachItem.nullifyLinks();
					} else
						System.out.println(eachItem.getName() + " already exists. Cannot overwrite files or directories");
				}
			} else
				pathDNE(itemName, "");

		} catch (Exception e) {

			pathDNE(target, "");
		}
		resetValues(temp);		
	}

	/**
	 * Copies the directory or file to the given path.
	 * 
	 * Copies the directory or a file to the given path. All the
	 * the links remain intact but are not linked to the copy but 
	 * to the original file or folder. The copied links are not broken.
	 * If the command is not given correctly an appropriate message 
	 * is printed or/and error is raised. If an outfile is mentioned 
	 * then an empty String is appended to the outfile contents. This 
	 * method uses another method to help copy the files and directories 
	 * called copyItemHelper.
	 * 
	 * @param copyPath String array that stores file or directory names to be
	 * 				   copied to the given destination.
	 */

	@SuppressWarnings("unused")
	private void copyItem(String[] copyPath) {

		Directory temp = currDir;
		String itemName = null;
		String[] cmdSorted = sortGuillemet(copyPath);
		ArrayList<SystemItem> items = new ArrayList<SystemItem>();

		if (counter == 0 || counter == 1)
			if (cmdSorted.length > 2) {
				for (i = 1; i < cmdSorted.length - 1; i++) {

					try {

						itemName = sortPath(cmdSorted[i]);
						if (itemName.equals(""))
							itemName = ".";

						if (currDir.getContents().containsKey(itemName))
							items.add(currDir.getContents().get(itemName));
						else
							pathDNE(itemName, "");

					} catch (Exception e) {

						pathDNE(cmdSorted[i], "");
					}
					resetValues(temp);
				}
				copyItemHelper(cmdSorted[cmdSorted.length - 1], items);
			} else
				detectInvalidCmd(cmdSorted, "cp", 2);

		outputFile();
	}
	
	/**
	 * Copy the directories or files to the target path.
	 * 
	 * Copies the directory or a file to the given path from parameter target.
	 * All the the links remain intact but are not linked to the copy but 
	 * to the original file or folder. If the target doesn't exist an appropriate 
	 * message is printed or/and error is raised.
	 * 
	 * @param target String that stores the target destination where 
	 * 				 the files or directories will be copied.
	 * @param itemsCopied ArrayList<SystemItem> that stores the files or directories 
	 * 					 being copied.
	 */

	private void copyItemHelper(String target, ArrayList<SystemItem> itemsCopied) {

		Directory temp = currDir;
		String itemName = null;
		Iterator<SystemItem> itr = itemsCopied.iterator();
		Directory storeTemp = null;
		SystemItem eachItem = null;

		try {

			itemName = sortPath(target);
			if (itemName.equals(""))
				itemName = ".";

			if (currDir.getContents().containsKey(itemName)){

				storeTemp = (Directory) currDir.getContents().get(itemName);

				if (storeTemp.getLink())
					storeTemp = (Directory) storeTemp.getLinkedTo();

				while (itr.hasNext()) {

					eachItem = itr.next();
					if (! storeTemp.getContents().containsKey(eachItem.getName()))
						storeTemp.addItem(eachItem.getName(), eachItem.copy(storeTemp));
					else
						System.out.println(eachItem.getName() + " already exists. Cannot overwrite files or directories");
				}
			} else
				pathDNE(itemName, "");

		} catch (Exception e) {

			pathDNE(target, "");
		}
		resetValues(temp);		
	}

	/**
	 * Creates links to the contents of the source directory or file.
	 * 
	 * Creates links to the contents of the source directory or file at the 
	 * given path. If the command is not given correctly an appropriate message
	 * is printed or/and error is raised. Call a helper method, linkItemHelper 
	 * to get the links made.
	 * 
	 * @param linkPath String array that stores file or directory links to be
	 * 				   created of the given source.
	 */

	@SuppressWarnings("unused")
	private void linkTo(String[] linkPath) {

		Directory temp = currDir;
		String itemName = null;
		String[] cmdSorted = sortGuillemet(linkPath);
		ArrayList<Directory> items = new ArrayList<Directory>();
		ArrayList<String> itemNames = new ArrayList<String>();

		if (counter == 0 || counter == 1)
			if (cmdSorted.length > 2) {
				for (i = 1; i < cmdSorted.length - 1; i++) {

					try {

						itemName = sortPath(cmdSorted[i]);
						if (itemName.equals(""))
							itemName = ".";
						if (!currDir.getContents().contains(itemName)) {

							if (currDir.getLink())
								currDir = (Directory) currDir.getLinkedTo();
								
							items.add(currDir);
							itemNames.add(itemName);

						} else
							System.out.println(cmdSorted[i] + "already exists. Cannot create link.");

					} catch (Exception e) {

						pathDNE(cmdSorted[i], "");
					}
					resetValues(temp);
				}
				linkItemHelper(cmdSorted[cmdSorted.length - 1], items, itemNames);
			} else
				detectInvalidCmd(cmdSorted, "mv", 2);

		outputFile();
	}

	/**
	 * Creates links to the contents of the source directory or file
	 * 
	 * Creates links to the contents of the source directory or file at the 
	 * given path. If target doesn't exist an appropriate message
	 * is printed or/and error is raised.
	 * 
	 * @param target String that stores the target destination to which the links
	 * 				 should be made.
	 * @param linkParents ArrayList<SystemItem> stores the parent directories if 
	 * 					  the links.
	 * @param linkParents ArrayList<SystemItem> stores the names of the links.
	 */

	private void linkItemHelper(String target, ArrayList<Directory> linkParents, ArrayList<String> itemNames) {

		Directory temp = currDir;
		String itemName = null;
		SystemItem storeTemp = null;
		SystemItem eachItem = null;

		try {

			itemName = sortPath(target);
			if (itemName.equals(""))
				itemName = ".";
			if (currDir.getContents().containsKey(itemName)) {
				
				storeTemp = currDir.getContents().get(itemName);

				for (i = 0; i < itemNames.size(); i++) {

					if (storeTemp.getClass().getName().equals("Directory"))
						eachItem = new Directory(itemNames.get(i), linkParents.get(i));
					else 
						eachItem = new File(itemNames.get(i), linkParents.get(i));
					
					linkParents.get(i).addItem(itemNames.get(i), eachItem);
					storeTemp.getLinksMade().add(eachItem);
					eachItem.isLink();
					eachItem.setLinkedTo(storeTemp.getLinkedTo());
					eachItem.linkContents(storeTemp.getContentObject());
				}
			} else
				pathDNE(itemName, "");

		} catch (Exception e) {

			pathDNE(target, "");
			e.printStackTrace();
		}
		resetValues(temp);		
	}
	
	/**
	 * Recursively search path (if it is a Directory) for file names containing a specific String.
	 * If path is File just check whether a name of File contains specific String in it
	 * If the command is not given correctly 
	 * an appropriate message is printed or/and error is raised.
	 * @param dirNamePath String array that stores the path and specific string user is searching for.
	 */
	@SuppressWarnings("unused")
    private void findString(String[] dirNamePath) {
        
        String[] cmdSorted = sortGuillemet(dirNamePath);
        String itemName = null;
        SystemItem path = null; // path where I am searching for a string
        Directory temp = currDir;
 
        if (counter == 0 || counter == 1) // user can only write one outfile where the output would be printed 
        {
            if (cmdSorted.length > 2) { // minimum length of input of user input
                String stringToFind = cmdSorted[1]; // string we are trying to find
                
                for (int i = 2; i < cmdSorted.length; ++i) {
                    try {
                        itemName = sortPath(cmdSorted[i]);
                        if (itemName.equals(""))
        					itemName = ".";
                        path = currDir.getContents().get(itemName);
                        
                        // if path is a Directory
                        if (path.getClass().getName().equals("Directory")) {
                        	currDir = (Directory) path;
                            findStringHelper(stringToFind);
                            }
                        //if path is a File
                        else if (path.getClass().getName().equals("File")){
                        	String file = findInFile((File) path, stringToFind);
                        	if (!file.equals("")) {
                        		output += currDir.getFullPath() + "/" + file + "\n";
                        	}
                        }
                    }
                    catch (Exception e) {
                        pathDNE(cmdSorted[i], ""); // the path does not exist
                    }
                    resetValues(temp);
                }
            } 
            else
                detectInvalidCmd(cmdSorted, "find", 1); // not enough parameters in user input
        }
        
        outputFile(); // if outfile was mentioned in user input , then write output of command to that outfile
    }
	
	/**
	 * Helper function of findString which recursively searches for file names in current Directory
	 * containing String stringToFind.
	 * @param stringToFind String we are searching for.
	 */
	private void findStringHelper(String stringToFind) {
		Directory currDirParent = currDir.getParent();
		ArrayList<String> toFind = new ArrayList<String>(); // contents of current Directory
		for (String content : currDir.getContents().keySet()) {
			toFind.add(content);
		}
		
		for (String content2 : toFind) {
			if (!content2.equals(".") && !content2.equals("..")){
				// if content2 is Directory recursively find String stringToFind in it.
				if (currDir.getContents().get(content2).getClass().getName().equals("Directory")) {
					currDir = (Directory) currDir.getContents().get(content2);
					findStringHelper(stringToFind);
				}
				// if content2 is File check if it contains String stringToFind in it.
				else if (currDir.getContents().get(content2).getClass().getName().equals("File")) {
					File f = (File) currDir.getContents().get(content2);
					String file = findInFile(f, stringToFind);
					// if it contains it print name including the path of file. Otherwise do nothing.
					if (!file.equals("")) {
						output += currDir.getFullPath() + "/" + file + "\n";
					}
				}
 			}
		}
		currDir = currDirParent; // go back to the previous current Directory
	}
	
	/**
	 * Helper function of findString and _findString methods. It checks whether a name of File f contains String str in it.
	 * @param f File
	 * @param str String we are searching for.
	 * @return name of File f if it contains str in it. Otherwise return "".
	 */
	private String findInFile(File f, String str) {
		if (f.getName().indexOf(str) >=0 ) {
			return f.getName();
		}
		else {
			return ""; // it is not in file
		}
	}
	
	/**
	 * Removes path, which can be either Directory or File.
	 * Confirms with the user that they want to delete path, and if so, removes it from the file system. 
	 * If path is a directory, it recursively removes all files and directories in it, prompting for confirmation for each one. 
	 * If -f is supplied, removes everything without confirmation.
	 * @param cmdRemove String array that stores the paths and directories to be removed.
	 */
	@SuppressWarnings("unused")
	private void removeItem(String[] cmdRemove) {

		String[] cmdSorted = sortGuillemet(cmdRemove);
        String itemName = null; // the name of SystemItem object I am removing
        Directory temp = currDir;
        Boolean checkForConfirmation = true; // if we need confirmation from user
        int index = 1; //starting point of all paths
 
        if (counter == 0 || counter == 1) // user can only write one outfile where the output would be printed 
        {
            if (cmdSorted.length >= 2) { // minimum length of input of user input
                
            	if (cmdSorted[1].equals("-f")) {
        			index = 2;
        			checkForConfirmation = false; // do not need confirmation
        		}
            	
                for (int i = index; i < cmdSorted.length; ++i) {
                    try {
                        itemName = sortPath(cmdSorted[i]); // SystemItem object we want to remove
                        if (!itemName.equals(".") && currDir != root)
                        	removeItemHelper(itemName, checkForConfirmation);
                        else
                        	System.out.println("Cannot remove root directory.");
                        
                    }
                    catch (Exception e) {
                        pathDNE(cmdSorted[i], ""); // the path does not exist
                    }
                    resetValues(temp); // reset all values
                }
            } 
            else
                detectInvalidCmd(cmdSorted, "rm", 1); // not enough parameters in user input
        }

        outputFile(); // if outfile was mentioned in user input , then write output of command to that outfile
	}
	
	/**
	 * Helper function of removeItem and removeDirectory functions which identifies 
	 * whether SystemItem Object is File or Directory and calls appropriate helper method to remove it.
	 * 
	 * @param name String which is name of SystemItem Object
	 * 
	 * @param checkForConformation Boolean that is True if confirmation from user is needed to delete SystemItem Object.
	 * If it is False, then this function deletes SystemItem Object without confirmation.
	 */
	private void removeItemHelper(String name, Boolean checkForConformation) {

		if (currDir.getContents().get(name).getClass().getName().equals("File")) { // SystemItem object is File
			removeFile(name, checkForConformation);
			}
		else { // otherwise it is Directory
			currDir = (Directory) currDir.getContents().get(name);
			removeDirectory(checkForConformation);
			}
	}
	
	/**
	 * Helper function of _removeItem method which recursively deletes all contents of the current Directory
	 * 
	 * @param checkForConformation Boolean that is True if confirmation from user is needed to delete SystemItem Object
	 * (contents of current Directory). 
	 * If it is False, then this function deletes SystemItem object without confirmation.
	 */
	private void removeDirectory(Boolean checkForConformation) {
		// The empty Directory is Directory with only two objects in it : "." and ".."
		if (currDir.getContents().size() == 2 || currDir.getLink()) {
			removeDirectoryHelper(currDir.getName(), checkForConformation);
		}
		
		else {
			ArrayList<String> toDelete = new ArrayList<String>(); // contents of current Directory which we need to delete
			for (String content : currDir.getContents().keySet()) {
				toDelete.add(content);
			}
			for (String content2 : toDelete) {
				if (!content2.equals(".") && !content2.equals("..")){
					removeItemHelper(content2, checkForConformation); // remove each item in the directory
				}
			}
			removeDirectoryHelper(currDir.getName(), checkForConformation); //delete itself after
		}
	}
	
	/**
	 * Helper function of removeDirectory function, which deletes a particular Directory from current Directory.
	 * 
	 * @param name String which is the name of the Directory we want to delete.
	 * 
	 * @param checkForConformation Boolean that is True if confirmation from user is needed to delete Directory. 
	 * If it is False, then this function deletes Directory without confirmation.
	 */
	private void removeDirectoryHelper(String name, Boolean checkForConformation) {
		Scanner in = new Scanner(System.in);
		String answer;
		Directory currDirParent = currDir.getParent();
		
		// check for confirmation from user to delete Directory
		if (checkForConformation == true) {
			System.out.print("Are you sure You want to delete directory " + currDir.getFullPath() + " Yes(y), No(n) ");
			answer = in.nextLine();
			
			// if answer is Yes, delete Directory. Otherwise do nothing.
			if (answer.equals("y")) {
				currDir.nullifyLinks(); //make all links to it invalid
				currDirParent.removeItem(name);
				}
			}
		else {
			currDir.nullifyLinks(); //make all links to it invalid
			currDirParent.removeItem(name); // remove without confirmation
		}
		currDir = currDirParent; // to do recursion, we need to make current Directory its parent.
	}
	
	/**
	 * Helper function of _removeItem function, which deletes a particular File from current Directory.
	 * 
	 * @param name String which is the name of the File we want to delete.
	 * 
	 * @param checkForConformation Boolean that is True if confirmation from user is needed to delete File. 
	 * If it is False, then this function deletes File without confirmation.
	 */
	private void removeFile(String name, Boolean checkForConformation) {
		Scanner in = new Scanner(System.in);
		String answer;
		
		// check for confirmation from user to delete File
		if (checkForConformation == true) {
			System.out.print("Are you sure You want to delete file " + currDir.getFullPath() + "/" + name + " Yes(y), No(n) ");
			answer = in.nextLine();
			// if answer is Yes, delete File. Otherwise do nothing.
			if (answer.equals("y")) {
				currDir.getContents().get(name).nullifyLinks(); //make all links to it invalid
				currDir.removeItem(name);
			}
		}
		else {
			
			currDir.nullifyLinks(); //make all links to it invalid
			currDir.removeItem(name); // remove without confirmation
		}
	}
}
